本系列文章已出版實體書籍:
「你的地圖會說話?WebGIS 與 JavaScript 的情感交織」(博碩文化)
WebGIS啟蒙首選✖五家地圖API✖近百個程式範例✖實用簡易口訣✖學習難度分級✖補充ES6小知識
展點
,把一堆點資料展在地圖上。
如果今天點的數量很多很多,除了圖台效能受到影響外,
視覺上也不美觀,那要怎麼樣呈現才能解決點多到爆的問題呢?
那就用群聚
吧!我們讓聚集的點用同一個圖標來表示,
並且用圖標的數字、大小、顏色,讓視覺化上更加鮮明,
讓人一眼就能看出哪裡是比較密集的地區!
今天要來介紹Leaflet API plugins的markercluster! (Leaflet Plugins)
讓展點通通群聚吧!
久違的Leaflet API,幫大家複習一下初始化地圖!
↓ 引入leaflet的css與js
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
↓ css 讓地圖滿板,沒有邊界
#lmap {
height: 100%;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
↓ 新增一個存放地圖的div
<div id="lmap"></div>
↓ 初始化地圖
var LMap = L.map(document.getElementById('lmap'), {
center: [23.5, 121],
zoom: 7,
crs: L.CRS.EPSG3857,
});
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
maxZoom: 18,
id: 'mapbox.streets'
}).addTo(LMap);
由於要測試群聚,因此寫一個亂數點產生器,來產生無數的點。
↓ 隨機在最小值(min)與最大值(max)之間產生數值
function random(min, max) {
return Math.random() * (max - min) + min;
}
Math.random()會隨機產生介於0~1的小數點,做為縮放比例。
再乘以變量(最大減最小),最後再加上最小值,就能取得範圍中的任意數。
↓ 新增 CreatePoint 在固定範圍內產生隨機經緯度的點,並存進陣列中。
let arr = [];
function CreatePoint(count) { // count為產生的點數量
for (let i = 0; i < count; i++) {
let longitude = random(120.5, 121.4); // 經度介於120.5~121.4
let latitude = random(23, 24.6); // 緯度介於23~24.6
arr.push({ x: longitude, y: latitude });
}
}
↓ 呼叫,產生1500個點。
CreatePoint(1500);
console.log(arr);
↓ 結果
↓ 將arr陣列跑迴圈,把所有點秀在地圖上。
arr.map(item => L.marker(new L.LatLng(item.y, item.x)))
.forEach(item => LMap.addLayer(item));
↓ 結果
密密麻麻的!密集恐懼症的別看~
↓ 引入 Leaflet MarkerCluster的css與js
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css" />
<script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js"></script>
↓ 首先,先新增一個L.markerClusterGroup,它可以存入許多座標點,並且讓它們群聚起來!
var markers = L.markerClusterGroup();
↓ 將arr陣列跑迴圈,這次把marker加入L.markerClusterGroup中,而不是直接加入地圖!
arr.map(item => L.marker(new L.LatLng(item.y, item.x)) // 新增Marker
.bindPopup(`<p>經度: ${item.x}</p><p>緯度: ${item.y}</p>`)) // 資訊視窗
.forEach(item => markers.addLayer(item)); // 把marker加入 L.markerClusterGroup中
↓ 將剛剛存放所有點的L.markerClusterGroup加入地圖中
LMap.addLayer(markers);
↓ 結果
↓ MarkerCluster的css要把剛剛引入的MarkerCluster.Default.css換掉,改引入MarkerCluster.cssunpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.css" />
↓ 將L.markerClusterGroup的參數加入iconCreateFunction,並且用cluster.getChildCount()計算群聚點數量。
var markers = L.markerClusterGroup({
iconCreateFunction: function (cluster) {
const number = cluster.getChildCount();
return L.divIcon({ html: number, className:
'cluster cluster-yellow', iconSize: L.point(25, 25) });
}
});
↓ 可以用css自己刻群聚圖標的樣式,懂SCSS或其他預處理器的人也可以用它們刻。
.cluster {
border: 2px solid grey;
border-radius: 35px;
display: flex;
align-items: center;
justify-content: center;
}
.cluster.cluster-green {
background-color: green;
}
.cluster.cluster-yellow {
background-color: yellow;
}
.cluster.cluster-red {
background-color: red;
}
↓ 結果換成自己刻的icon了!
↓ 可是要依照不同的數量給予icon不同的大小及顏色,因此加入自定義的icon邏輯。
function IconLogic(number) { // 數量
let className = 'cluster';
let point;
if (number < 100) {
className += ' cluster-green';
point = L.point(25, 25);
} else if (number < 200) {
className += ' cluster-yellow';
point = L.point(30, 30);
} else {
className += ' cluster-red';
point = L.point(35, 35);
}
return {
className: className,
point: point
}
}
↓ 將群聚icon的參數改為變數,加入剛剛寫好的icon邏輯。
var markers = L.markerClusterGroup({
iconCreateFunction: function (cluster) {
const number = cluster.getChildCount();
let icon = IconLogic(number);
return L.divIcon({ html: number, className: icon.className
, iconSize: icon.point });
});
↓ 結果
↓ 放大
↓ 再放大成一般icon後點擊會顯示資訊視窗
↓ 在L.markerClusterGroup物件上綁定clusterclick,可以幫群聚點新增點擊事件。
markers.on('clusterclick', function (e) {
const number = e.layer.getAllChildMarkers().length;
console.log('群聚數量: ' + number);
});
callback後的e.layer.getAllChildMarkers()可以取得該群聚點數量。
↓ console 群聚點數量
↓ e.layer.getConvexHull()可以取得群聚點計算該區塊群聚數量的邊界。
var polySelected;
markers.on('clusterclick', function (e) {
const number = e.layer.getAllChildMarkers().length;
if (polySelected) { // 如果有選取的邊界存在,先清除
LMap.removeLayer(polySelected);
}
polySelected = L.polygon(e.layer.getConvexHull()); // 繪出邊界
LMap.addLayer(polySelected);
console.log(e.layer.getConvexHull());
console.log('群聚數量: ' + number);
});
↓ console 邊界座標
↓ 點擊群聚點後繪出邊界。
密集恐懼症請左轉!
↓ e.layer.spiderfy()可以劃出群聚點與群聚內各個點的連線,俗稱蜘蛛網。
var polySelected;
markers.on('clusterclick', function (e) {
const number = e.layer.getAllChildMarkers().length;
if (number < 100) { // 群聚數量小於100才繪製蜘蛛網
e.layer.spiderfy();
}
if (polySelected) {
LMap.removeLayer(polySelected);
}
polySelected = L.polygon(e.layer.getConvexHull())
LMap.addLayer(polySelected);
console.log(e.layer.getConvexHull());
console.log('群聚數量: ' + number);
});
↓ 結果
L.markerClusterGroup其他設定
今天介紹了有趣的Leaflet MarkerCluster,
明天再來繼續玩玩看其他有趣的Leaflet Plugins吧!
大家看到那個蜘蛛網是覺得很噁想吐?還是覺得很潮呢? (笑